﻿using Colorful.Database;
using System;
using System.Collections.Generic;
using System.Text;
using MongoDB.Driver;
using Colorful.Models;
using System.Diagnostics;
using System.Linq;

namespace Colorful.CMS.Core
{
    public class ReportService
    {
        private DbContext _db;

        public ReportService(DbContext db)
        {
            _db = db;
        }

        #region 生成报表
        /// <summary>
        /// 生成报表指定类型报表并返回生成时间
        /// </summary>
        /// <param name="db">数据操作对象</param>
        /// <param name="reportType">报表类型</param>
        /// <returns></returns>
        public long GenerateReport(ReportType reportType)
        {
            var watch = new Stopwatch();


            //var pv = query.GroupBy("new (Province,AddDate.ToString(\"%Y/%m/%d %H:%M\") as Date)").Select("new(Key.Province,Key.Date, Count() as PV)");
            //var pvResult = pv.ToDynamicList();

            //var uv = query.GroupBy("new (Province,TrackingId,AddDate.ToString(\"%Y/%m/%d %H:%M\") as Date)").Select("new(Key.Province,Key.Date, Count() as PV)");
            //var uvResult = uv.ToDynamicList();

            //var ip = query.GroupBy("new (Province,IP,AddDate.ToString(\"%Y/%m/%d %H:%M\") as Date)").Select("new(Key.Province,Key.Date, Count() as PV)");
            //var ipResult = ip.ToDynamicList();
            DateTime? lastDate = null;
            string key = $"{reportType}";
            var lastUpdate = GetLastUpdate(_db, key);
            var query = _db.VisitorRecords.Where(a => a.AddDate > lastUpdate);
            if (query.Any())
            {
                var dateFormat = "%Y-%m-%dT%H:00:00Z";
                lastDate = query.Max(a => a.AddDate);
                switch (reportType)
                {
                    case ReportType.UVPV:
                        #region UV PV IP
                        {
                            var timeFormat = "%H";
                            var pv = query.GroupBy(a => new { Date = a.AddDate.ToString(dateFormat), Time = a.AddDate.ToString(timeFormat) }).Select(a => new { a.Key.Date, a.Key.Time, Count = a.Count(), RL = a.Sum(b => b.RequestLength), RSL = a.Sum(b => b.ResponseLength) }).ToList();
                            var uv = query.GroupBy(a => new { a.TrackingId, Date = a.AddDate.ToString(dateFormat), Time = a.AddDate.ToString(timeFormat) }).Select(a => new { a.Key.Date, a.Key.Time, Count = a.Count(), RL = a.Sum(b => b.RequestLength), RSL = a.Sum(b => b.ResponseLength) });
                            var ip = query.GroupBy(a => new { a.IP, Date = a.AddDate.ToString(dateFormat), Time = a.AddDate.ToString(timeFormat) }).Select(a => new { a.Key.Date, a.Key.Time, Count = a.Count(), RL = a.Sum(b => b.RequestLength), RSL = a.Sum(b => b.ResponseLength) });

                            var result = pv.Select(a => new Statistic() { Id = a.Date.GetHash().ToLower(), AddDate = DateTime.Now, Date = DateTime.Parse(a.Date).ToLocalTime(), RequestLength = a.RL, ResponseLength = a.RSL, PV = a.Count, UV = uv.Count(b => b.Date == a.Date), IP = ip.Count(b => b.Date == a.Date) }).ToList();
                            result.AsParallel().ForAll(item =>
                            {
                                item.Key = item.Date.ToString("HH:00");
                            });
                            if (lastUpdate < DateTime.Now.MinDate())
                            {
                                try
                                {
                                    _db.Statistics.InsertMany(result, new InsertManyOptions() { BypassDocumentValidation = true, IsOrdered = false });
                                }
                                catch (Exception ex)
                                {
                                    WriteLog(ex);
                                }
                            }
                            else
                            {
                                foreach (var item in result)
                                {
                                    _db.Statistics.Save(item);
                                }
                            }
                        }
                        #endregion
                        break;
                    case ReportType.Country:
                        #region 按国家生成
                        {
                            var pv = query.GroupBy(a => new { a.Country, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Country, a.Key.Date, Count = a.Count() }).ToList();
                            var uv = query.GroupBy(a => new { a.Country, a.TrackingId, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Country, a.Key.Date, Count = a.Count() });
                            var ip = query.GroupBy(a => new { a.Country, a.IP, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Country, a.Key.Date, Count = a.Count() });

                            var result = pv.Select(a => new StatByCountry() { Id = $"{a.Country}{a.Date}".GetHash().ToLower(), Key = a.Date, Country = a.Country, AddDate = DateTime.Now, Date = DateTime.Parse(a.Date), PV = a.Count, UV = uv.Count(b => b.Date == a.Date), IP = ip.Count(b => b.Date == a.Date) }).ToList();
                            if (lastUpdate < DateTime.Now.MinDate())
                            {
                                try
                                {
                                    _db.StatByCountries.InsertMany(result, new InsertManyOptions() { BypassDocumentValidation = true, IsOrdered = false });
                                }
                                catch (Exception ex)
                                {
                                    WriteLog(ex);
                                }
                            }
                            else
                            {
                                foreach (var item in result)
                                {
                                    _db.StatByCountries.Save(item);
                                }
                            }
                        }
                        #endregion
                        break;
                    case ReportType.Area:
                        #region 按地区生成（省）
                        {
                            var pv = query.GroupBy(a => new { a.Province, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Province, a.Key.Date, Count = a.Count() }).ToList();
                            var uv = query.GroupBy(a => new { a.Province, a.TrackingId, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Province, a.Key.Date, Count = a.Count() });
                            var ip = query.GroupBy(a => new { a.Province, a.IP, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Province, a.Key.Date, Count = a.Count() });

                            var result = pv.Select(a => new StatByArea() { Id = $"{a.Province}{a.Date}".GetHash().ToLower(), Key = a.Date, Area = a.Province, AddDate = DateTime.Now, Date = DateTime.Parse(a.Date), PV = a.Count, UV = uv.Count(b => b.Date == a.Date), IP = ip.Count(b => b.Date == a.Date) }).ToList();
                            if (lastUpdate < DateTime.Now.MinDate())
                            {
                                try
                                {
                                    _db.StatByAreas.InsertMany(result, new InsertManyOptions() { BypassDocumentValidation = true, IsOrdered = false });
                                }
                                catch (Exception ex)
                                {
                                    WriteLog(ex);
                                }
                            }
                            else
                            {
                                foreach (var item in result)
                                {
                                    _db.StatByAreas.Save(item);
                                }
                            }
                        }
                        #endregion
                        break;
                    case ReportType.Domain:
                        #region 按域名
                        {
                            var pv = query.GroupBy(a => new { a.Domain, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Domain, a.Key.Date, Count = a.Count() }).ToList();
                            var uv = query.GroupBy(a => new { a.Domain, a.TrackingId, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Domain, a.Key.Date, Count = a.Count() });
                            var ip = query.GroupBy(a => new { a.Domain, a.IP, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Domain, a.Key.Date, Count = a.Count() });

                            var result = pv.Select(a => new StatByDomain() { Id = $"{a.Domain}{a.Date}".GetHash().ToLower(), Key = a.Date, Domain = a.Domain, AddDate = DateTime.Now, Date = DateTime.Parse(a.Date), PV = a.Count, UV = uv.Count(b => b.Date == a.Date), IP = ip.Count(b => b.Date == a.Date) }).ToList();
                            if (lastUpdate < DateTime.Now.MinDate())
                            {
                                try
                                {
                                    _db.StatByDomains.InsertMany(result, new InsertManyOptions() { BypassDocumentValidation = true, IsOrdered = false });
                                }
                                catch (Exception ex)
                                {
                                    WriteLog(ex);
                                }
                            }
                            else
                            {
                                foreach (var item in result)
                                {
                                    _db.StatByDomains.Save(item);
                                }
                            }
                        }
                        #endregion
                        break;
                    case ReportType.Page:
                        #region 按页面
                        {
                            var pv = query.GroupBy(a => new { a.Page, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Page, a.Key.Date, Count = a.Count() }).ToList();
                            var uv = query.GroupBy(a => new { a.Page, a.TrackingId, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Page, a.Key.Date, Count = a.Count() });
                            var ip = query.GroupBy(a => new { a.Page, a.IP, Date = a.AddDate.ToString(dateFormat) }).Select(a => new { a.Key.Page, a.Key.Date, Count = a.Count() });

                            var result = pv.Select(a => new StatByPage() { Id = $"{a.Page}{a.Date}".GetHash().ToLower(), Key = a.Date, Page = a.Page, AddDate = DateTime.Now, Date = DateTime.Parse(a.Date), PV = a.Count, UV = uv.Count(b => b.Date == a.Date), IP = ip.Count(b => b.Date == a.Date) }).ToList();
                            if (lastUpdate < DateTime.Now.MinDate())
                            {
                                try
                                {
                                    _db.StatByPages.InsertMany(result, new InsertManyOptions() { BypassDocumentValidation = true, IsOrdered = false });
                                }
                                catch (Exception ex)
                                {
                                    WriteLog(ex);
                                }
                            }
                            else
                            {
                                foreach (var item in result)
                                {
                                    _db.StatByPages.Save(item);
                                }
                            }
                        }
                        #endregion
                        break;
                }
            }
            if (lastDate != null)
                _db.ReportSchedules.Update1(a => a.Id == key, b => b.LastUpdate, lastDate, true);

            watch.Stop();
            return watch.ElapsedMilliseconds;
        }
        #endregion

        #region 清除报表
        /// <summary>
        /// 清除报表
        /// </summary>
        /// <returns></returns>
        public long ClearReports()
        {
            var watch = new Stopwatch();
            _db.Statistics.Delete(a => a.AddDate < DateTime.Now.AddDays(-WebService.WebSetting.StatisticSaveTime));
            _db.StatByAreas.Delete(a => a.AddDate < DateTime.Now.AddDays(-WebService.WebSetting.StatisticSaveTime));
            _db.StatByCountries.Delete(a => a.AddDate < DateTime.Now.AddDays(-WebService.WebSetting.StatisticSaveTime));
            _db.StatByDomains.Delete(a => a.AddDate < DateTime.Now.AddDays(-WebService.WebSetting.StatisticSaveTime));
            _db.StatByPages.Delete(a => a.AddDate < DateTime.Now.AddDays(-WebService.WebSetting.StatisticSaveTime));
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }
        #endregion

        #region 私有方法
        private DateTime GetLastUpdate(DbContext db, string id)
        {
            DateTime lastUpdate;
            var rs = db.ReportSchedules.FirstOrDefault(a => a.Id == id);
            if (rs == null)
                lastUpdate = new DateTime(2000, 1, 1);
            else
                lastUpdate = rs.LastUpdate;
            return lastUpdate;
        }

        private void WriteLog(Exception ex)
        {
            WebService.WriteErrorLog(ex);
        }
        #endregion
    }
}
